////////////////////////////////////////////////////////////////////////
//
//     Copyright (c) 2009-2013 Denim Group, Ltd.
//
//     The contents of this file are subject to the Mozilla Public License
//     Version 2.0 (the "License"); you may not use this file except in
//     compliance with the License. You may obtain a copy of the License at
//     http://www.mozilla.org/MPL/
//
//     Software distributed under the License is distributed on an "AS IS"
//     basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//     License for the specific language governing rights and limitations
//     under the License.
//
//     The Original Code is ThreadFix.
//
//     The Initial Developer of the Original Code is Denim Group, Ltd.
//     Portions created by Denim Group, Ltd. are Copyright (C)
//     Denim Group, Ltd. All Rights Reserved.
//
//     Contributor(s): Denim Group, Ltd.
//
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.webapp.controller;

import java.util.Calendar;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.denimgroup.threadfix.data.entities.Application;
import com.denimgroup.threadfix.data.entities.Finding;
import com.denimgroup.threadfix.data.entities.Permission;
import com.denimgroup.threadfix.data.entities.SurfaceLocation;
import com.denimgroup.threadfix.data.entities.Vulnerability;
import com.denimgroup.threadfix.data.entities.VulnerabilityComment;
import com.denimgroup.threadfix.service.ApplicationService;
import com.denimgroup.threadfix.service.PermissionService;
import com.denimgroup.threadfix.service.SanitizedLogger;
import com.denimgroup.threadfix.service.VulnerabilityCommentService;
import com.denimgroup.threadfix.service.VulnerabilityService;

@Controller
@RequestMapping("/organizations/{orgId}/applications/{appId}/vulnerabilities")
public class VulnerabilityController {

	private VulnerabilityService vulnerabilityService;
	private ApplicationService applicationService;
	private VulnerabilityCommentService vulnerabilityCommentService;
	private PermissionService permissionService;
	
	private final SanitizedLogger log = new SanitizedLogger(VulnerabilityController.class);

	@Autowired
	public VulnerabilityController(PermissionService permissionService,
			ApplicationService applicationService,
			VulnerabilityService vulnerabilityService,
			VulnerabilityCommentService vulnerabilityCommentService) {
		this.vulnerabilityService = vulnerabilityService;
		this.vulnerabilityCommentService = vulnerabilityCommentService;
		this.permissionService = permissionService;
		this.applicationService = applicationService;
	}
	
	public VulnerabilityController(){}

	// switched to not show our guess as to static surfaceLocations - only show
	// surfaceLocation for dynamic scans.
	@RequestMapping(value = "/{vulnerabilityId}", method = RequestMethod.GET)
	public String detail(@PathVariable("appId") int appId, @PathVariable("orgId") int orgId,
			@PathVariable("vulnerabilityId") int vulnerabilityId, Model model) {
		
		if (!permissionService.isAuthorized(Permission.READ_ACCESS,orgId,appId)) {
			return "403";
		}
		
		Vulnerability vulnerability = vulnerabilityService.loadVulnerability(vulnerabilityId);
		
		checkResourceNotFound(vulnerability, vulnerabilityId, appId);
		
		String[] times = vulnerabilityService.getTimeDifferences(vulnerability);
		SurfaceLocation surfaceLocation = vulnerabilityService
				.getSurfaceLocationFromDynamicFinding(vulnerability);
		List<Finding> staticFindingList = vulnerabilityService.getStaticFindings(vulnerability);

		// if there's only one static finding, display its data flow elements.
		Finding singleStaticFinding = null;
		if (staticFindingList != null && staticFindingList.size() == 1) {
			singleStaticFinding = staticFindingList.get(0);
			staticFindingList = null;
		}
		
		permissionService.addPermissions(model, orgId, appId, Permission.CAN_MODIFY_VULNERABILITIES);

		List<VulnerabilityComment> comments = vulnerabilityCommentService.loadAllForVuln(vulnerabilityId);
		
		model.addAttribute("singleStaticFinding", singleStaticFinding);
		model.addAttribute("comments", comments);
		model.addAttribute("staticFindingList", staticFindingList);
		model.addAttribute("surfaceLocation", surfaceLocation);
		model.addAttribute("vulnerability", vulnerability);
		model.addAttribute("timeArray", times);
		return "applications/vulnerability";
	}
	
	// switched to not show our guess as to static surfaceLocations - only show
	// surfaceLocation for dynamic scans.
	@RequestMapping(value = "/{vulnerabilityId}/addComment", method = RequestMethod.POST)
	public String addComment(@PathVariable("appId") int appId, @PathVariable("orgId") int orgId,
			@RequestParam("comments") String comments, Model model,
			@PathVariable("vulnerabilityId") int vulnerabilityId) {
		
		Vulnerability vulnerability = vulnerabilityService.loadVulnerability(vulnerabilityId);
		
		checkResourceNotFound(vulnerability, vulnerabilityId, appId);
		
		String result = vulnerabilityCommentService.addCommentToVuln(comments, vulnerabilityId);
		
		if (result == null) result = "The submitted comment was invalid.";
		
		if (!result.equals(VulnerabilityCommentService.VALID)) {
			model.addAttribute("commentError", result);
		}
		
		List<VulnerabilityComment> commentList = vulnerabilityCommentService.loadAllForVuln(vulnerabilityId);
		
		model.addAttribute("vulnerability", vulnerability);
		model.addAttribute("comments", commentList);
		
		return "applications/vulnComments";
	}

	@RequestMapping(value = "/{vulnerabilityId}/defect", method = RequestMethod.GET)
	public String viewDefect(@PathVariable("appId") int appId, @PathVariable("orgId") int orgId,
			@PathVariable("vulnerabilityId") int vulnerabilityId, ModelMap model) {
		
		if (!permissionService.isAuthorized(Permission.READ_ACCESS,orgId,appId)) {
			return "403";
		}
		
		Vulnerability vulnerability = vulnerabilityService.loadVulnerability(vulnerabilityId);
		
		checkResourceNotFound(vulnerability, vulnerabilityId, appId);
		
		if (vulnerability.getDefect() == null) {
			log.warn("The requested Vulnerability did not have an associated Defect, returning to the Vulnerability page.");
			return "redirect:/organizations/" + orgId + "/applications/" + appId + "/vulnerabilities/" + vulnerabilityId;
		}
		
		model.addAttribute("defect", vulnerability.getDefect());
		return "applications/defects";
	}
	
	@RequestMapping(value = "/{vulnerabilityId}/close", method = RequestMethod.GET)
	public String closeVulnerability(@PathVariable("vulnerabilityId") int vulnerabilityId, 
			@PathVariable("appId") int appId, @PathVariable("orgId") int orgId, ModelMap model) {
		
		if (!permissionService.isAuthorized(Permission.CAN_MODIFY_VULNERABILITIES, orgId, appId)) {
			return "403";
		}
		
		Vulnerability vulnerability = vulnerabilityService.loadVulnerability(vulnerabilityId);
		
		checkResourceNotFound(vulnerability, vulnerabilityId, appId);
		
		vulnerability.closeVulnerability(null, Calendar.getInstance());
		vulnerability.setFoundByScanner(false);
		vulnerabilityService.storeVulnerability(vulnerability);
		return "redirect:/organizations/" + orgId + "/applications/" + appId + "/vulnerabilities/" + vulnerabilityId;
	}
	
	@RequestMapping(value = "/{vulnerabilityId}/open", method = RequestMethod.GET)
	public String openVulnerability(@PathVariable("vulnerabilityId") int vulnerabilityId, 
			@PathVariable("appId") int appId, @PathVariable("orgId") int orgId, ModelMap model) {
		
		if (!permissionService.isAuthorized(Permission.CAN_MODIFY_VULNERABILITIES, orgId, appId)) {
			return "403";
		}
		
		Vulnerability vulnerability = vulnerabilityService.loadVulnerability(vulnerabilityId);
		
		checkResourceNotFound(vulnerability, vulnerabilityId, appId);
		
		vulnerability.setActive(true);
		vulnerabilityService.storeVulnerability(vulnerability);
		return "redirect:/organizations/" + orgId + "/applications/" + appId + "/vulnerabilities/" + vulnerabilityId;
	}
	
	@RequestMapping(value = "/{vulnerabilityId}/markFalsePositive", method = RequestMethod.GET)
	public String markFalsePositive(@PathVariable("vulnerabilityId") int vulnerabilityId, 
			@PathVariable("appId") int appId, @PathVariable("orgId") int orgId, ModelMap model) {
		if (!permissionService.isAuthorized(Permission.CAN_MODIFY_VULNERABILITIES, orgId, appId)) {
			return "403";
		}
		
		Vulnerability vulnerability = vulnerabilityService.loadVulnerability(vulnerabilityId);
		
		checkResourceNotFound(vulnerability, vulnerabilityId, appId);
		
		vulnerability.setIsFalsePositive(true);
		vulnerabilityService.storeVulnerability(vulnerability);
		return "redirect:/organizations/" + orgId + "/applications/" + appId + "/vulnerabilities/" + vulnerabilityId;
	}
	
	@RequestMapping(value = "/{vulnerabilityId}/markNotFalsePositive", method = RequestMethod.GET)
	public String markNotFalsePositive(@PathVariable("vulnerabilityId") int vulnerabilityId, 
			@PathVariable("appId") int appId, @PathVariable("orgId") int orgId, ModelMap model) {
		
		if (!permissionService.isAuthorized(Permission.CAN_MODIFY_VULNERABILITIES, orgId, appId)) {
			return "403";
		}
		
		Vulnerability vulnerability = vulnerabilityService.loadVulnerability(vulnerabilityId);
		
		checkResourceNotFound(vulnerability, vulnerabilityId, appId);
		
		vulnerability.setIsFalsePositive(false);
		vulnerabilityService.storeVulnerability(vulnerability);
		return "redirect:/organizations/" + orgId + "/applications/" + appId + "/vulnerabilities/" + vulnerabilityId;
	}

	@RequestMapping(value = "/{vulnerabilityId}/mergeFindings", method = RequestMethod.GET)
	public String mergeFinding(@PathVariable("vulnerabilityId") int vulnerabilityId, ModelMap model,
			@PathVariable("appId") int appId, @PathVariable("orgId") int orgId) {
		
		if (!permissionService.isAuthorized(Permission.CAN_MODIFY_VULNERABILITIES, orgId, appId)) {
			return "403";
		}
		
		Vulnerability vulnerability = vulnerabilityService.loadVulnerability(vulnerabilityId);
		
		checkResourceNotFound(vulnerability, vulnerabilityId, appId);
		
		if (vulnerability.getFindings() != null
				&& vulnerability.getFindings().size() != 0
				&& vulnerability.getFindings().size() != 1) {
			List<Finding> findings = vulnerability.getFindings();
			model.addAttribute(findings);
		}
		model.addAttribute(vulnerability);
		return "/applications/vulnerability";
	}
	
	private void checkResourceNotFound(Vulnerability vuln, int vulnId, int appId) {
		Application application = applicationService.loadApplication(appId);
		
		if (vuln == null || application == null || !application.isActive()) {
			log.warn(ResourceNotFoundException.getLogMessage("Vulnerability", vulnId));
			throw new ResourceNotFoundException();
		}
	}

}
